#include "Pch.h"
#include "MainWindow.h"
#include "SharedStatusBar.h"
#include "PDBManager.h"
#include "RenderWindow.h"
#include "ResidueWindow.h"
#include "SequenceWindow.h"
#include "Molli16x16.xpm"
#include "Molli32x32.xpm"
#include "Config.h"
#include "FileRequest.h"
#include "AlignmentDialog.h"

// Only used so this file will know if the project's built with omp
// or not under Linux.  Doesn't even have to be included, just for
// my dependency resolution stuff.
#ifdef _OPENMP
#include <omp.h>
#endif

extern "C" {
	#include "Matt/MultipleAlignment.h"
	#include "Matt/Extend.h"
	#include "Matt/MultipleAlignmentOutput.h"
}
#include "ThirdParty/wxMozillaLikeAboutBoxDialog/wxMozillaLikeAboutBoxDialog.h"
#include "ThirdParty/wxMozillaLikeAboutBoxDialog/header_png.h"

enum MenuEntries {
	//ID_FILE_OPEN = 1,
	ID_FILE_OPENNEW = 1,
	ID_FILE_OPENWEB,
	ID_FILE_SAVEIMAGE,
	//ID_FILE_EXIT,


	ID_DISPLAY_CARTOON,
	ID_DISPLAY_BALLANDSTICK,
	ID_DISPLAY_SPACEFILL,
	ID_DISPLAY_HIDE,

	ID_DISPLAY_LABEL,

	ID_DISPLAY_HYDROGEN_BONDS,

	ID_DISPLAY_HIDE_UNSELECTED,

	ID_DISPLAY_ATOM_COLORS,
	ID_DISPLAY_COLORBYSTRUCTURE,
	ID_DISPLAY_COLORBYALIGNMENT,
	ID_DISPLAY_PICK_COLOR,

	ID_DISPLAY_CENTER_CAMERA,
	ID_DISPLAY_RESETZOOM,
	ID_DISPLAY_RESETCAMERAPOSITION,

	ID_DISPLAY_INVERT,

	ID_TOOLS_SETTINGS,
	ID_TOOLS_ALIGNMENT,
	ID_TOOLS_OPENALIGNMENT,

	ID_WINDOW_RESIDUE,
	ID_WINDOW_SEQUENCE,

	ID_ROTATION_ARCBALL,
	ID_ROTATION_SMOOTHED_ARCBALL,
	ID_ROTATION_DIRECT,

	// Molecular alignment codes
	ID_MOLECULAR_ALIGNMENT_BASE,
	ID_MA_SELECT_CORE,
	ID_MA_COLOR_ALIGNED,
};

struct MenuEntry {
	short depth;
	short id;
	const wxChar *text;
	wxItemKind kind;
};

wxMenu *CreateMenu(MenuEntry *&entry) {
	int depth = entry->depth+1;
	//wxMenu *menu = new wxMenu(entry->text, 0);
	wxMenu *menu = new wxMenu(0);
	entry++;
	while (1) {
		if (entry->depth < depth) break;
		else if (!entry->id) {
			const wxChar *text = entry->text;
			wxMenu *subMenu = CreateMenu(entry);
			if (subMenu) {
				menu->AppendSubMenu(subMenu, text);
			}
		}
		else {
			wxString text = entry->text;
			menu->Append(entry->id, text, wxEmptyString, entry->kind);
			entry++;
		}
	}
	return menu;
}

wxMenuBar *CreateMenuBar(MenuEntry *&entry) {
	int depth = entry->depth;
	wxMenuBar *menuBar = new wxMenuBar(0);
	while (1) {
		if (entry->depth < depth) break;
		else {
			const wxChar *text = entry->text;
			wxMenu *subMenu = CreateMenu(entry);
			if (subMenu) {
				menuBar->Append(subMenu, text);
			}
		}
	}
	return menuBar;
}

static MenuEntry mainWindowMenu[] = {
	{0,  wxID_ANY, _T("&File"), wxITEM_NORMAL},
		{1, wxID_OPEN, _T("&Open\tCtrl+O"), wxITEM_NORMAL},
		{1, ID_FILE_OPENNEW, _T("&Open in New Window\tCtrl+N"), wxITEM_NORMAL},
		{1, ID_FILE_OPENWEB, _T("Open &Web\tCtrl+W"), wxITEM_NORMAL},

		{1, wxID_SEPARATOR, _T(""), wxITEM_SEPARATOR},

		//{1, ID_FILE_SAVEIMAGE, _T("Save &Image\tCtrl+I"), wxITEM_NORMAL},

		//{1, wxID_SEPARATOR, _T(""), wxITEM_SEPARATOR},

		{1, wxID_EXIT, _T("&Exit\tCtrl+X"), wxITEM_NORMAL},
	{0,  wxID_ANY, _T("&Display"), wxITEM_NORMAL},
		{1, ID_DISPLAY_CARTOON, _T("Car&toon\tCtrl+C"), wxITEM_CHECK},
		{1, ID_DISPLAY_BALLANDSTICK, _T("&Ball and Stick\tCtrl+B"), wxITEM_CHECK},
		{1, ID_DISPLAY_SPACEFILL, _T("&Space Fill\tCtrl+F"), wxITEM_CHECK},
		{1, ID_DISPLAY_HIDE, _T("&Hide\tDel"), wxITEM_CHECK},

		{1, wxID_SEPARATOR, _T(""), wxITEM_SEPARATOR},

		{1, ID_DISPLAY_HIDE_UNSELECTED, _T("Hide all but &selected\tCtrl+H"), wxITEM_NORMAL},

		{1, wxID_SEPARATOR, _T(""), wxITEM_NORMAL},

		{1, ID_DISPLAY_LABEL, _T("&Label Residues"), wxITEM_CHECK},
		{1, ID_DISPLAY_HYDROGEN_BONDS, _T("Hydrogen Bonds"), wxITEM_CHECK},

		{1, wxID_SEPARATOR, _T(""), wxITEM_SEPARATOR},

		{1, ID_DISPLAY_ATOM_COLORS, _T("Use &Atom Colors"), wxITEM_CHECK},
		{1, ID_DISPLAY_COLORBYSTRUCTURE, _T("&Color by Structure"), wxITEM_CHECK},
		{1, ID_DISPLAY_COLORBYALIGNMENT, _T("C&olor by Alignment"), wxITEM_CHECK},
		{1, ID_DISPLAY_PICK_COLOR, _T("&Pick Color..."), wxITEM_NORMAL},

		{1, wxID_SEPARATOR, _T(""), wxITEM_NORMAL},

		{1, ID_DISPLAY_CENTER_CAMERA, _T("Cente&r Camera\t/"), wxITEM_NORMAL},
		{1, ID_DISPLAY_RESETZOOM, _T("Reset &Zoom"), wxITEM_NORMAL},
		{1, ID_DISPLAY_RESETCAMERAPOSITION, _T("&Reset Camera Position\t"), wxITEM_NORMAL},

	{0,  wxID_ANY, _T("&Tools"), wxITEM_NORMAL},
		{1, wxID_UNDO, _T("&Undo\tCtrl+Z"), wxITEM_NORMAL},
		{1, wxID_REDO, _T("&Redo\tCtrl+Shift+Z"), wxITEM_NORMAL},

		{1, wxID_SEPARATOR, _T(""), wxITEM_SEPARATOR},

		{1, ID_TOOLS_ALIGNMENT, _T("&Alignment...\tCtrl+M"), wxITEM_NORMAL},

		{1, wxID_SEPARATOR, _T(""), wxITEM_SEPARATOR},

		{1, ID_TOOLS_SETTINGS, _T("&Settings...\tAlt+S"), wxITEM_NORMAL},

	{0,  wxID_ANY, _T("&Windows"), wxITEM_NORMAL},
#ifdef WIN32
		{1, ID_WINDOW_RESIDUE, _T("&Residue"), wxITEM_CHECK},
#endif
		{1, ID_WINDOW_SEQUENCE, _T("Se&quence"), wxITEM_CHECK},
	{0,  wxID_ANY, _T("&Help"), wxITEM_NORMAL},
		//{1, wxID_HELP, _T("&Help"), wxITEM_NORMAL},
		{1, wxID_ABOUT, _T("&About"), wxITEM_NORMAL},
	{0,  wxID_ANY, _T("&Experimental"), wxITEM_NORMAL},
		{1, ID_ROTATION_ARCBALL, _T("&Arcball Rotation"), wxITEM_RADIO},
		{1, ID_ROTATION_SMOOTHED_ARCBALL, _T("&Smoothed Arcball Rotation"), wxITEM_RADIO},
		{1, ID_ROTATION_DIRECT, _T("&Direct Rotation"), wxITEM_RADIO},
	// Final entry.
	{-1,  wxID_SEPARATOR, _T(""), wxITEM_SEPARATOR}
};


MainWindow **mainWindows = 0;
int numMainWindows = 0;

BEGIN_EVENT_TABLE(MainWindow, wxFrame)
    EVT_MENU(wxID_OPEN, MainWindow::OnOpen)
    EVT_MENU(ID_FILE_OPENNEW, MainWindow::OnOpenNew)
    EVT_MENU(ID_FILE_OPENWEB, MainWindow::OnOpenWeb)
    EVT_MENU(wxID_EXIT, MainWindow::OnExit)
    EVT_MENU_RANGE(ID_DISPLAY_CARTOON, ID_DISPLAY_HIDE_UNSELECTED, MainWindow::OnChangeViz)
	EVT_MENU_RANGE(ID_DISPLAY_INVERT, ID_DISPLAY_INVERT, MainWindow::OnChangeViz)
    EVT_MENU_RANGE(ID_DISPLAY_CENTER_CAMERA, ID_DISPLAY_RESETCAMERAPOSITION, MainWindow::OnChangeCamera)
    EVT_MENU_RANGE(ID_DISPLAY_ATOM_COLORS, ID_DISPLAY_PICK_COLOR, MainWindow::OnChangeColor)
    EVT_MENU(wxID_REDO, MainWindow::OnUndo)
    EVT_MENU(wxID_UNDO, MainWindow::OnUndo)

    EVT_MENU_RANGE(ID_ROTATION_ARCBALL, ID_ROTATION_DIRECT, MainWindow::OnToggleSetting)

	EVT_MENU_RANGE(ID_WINDOW_RESIDUE, ID_WINDOW_SEQUENCE, MainWindow::OnToggleWindow)

    EVT_MENU(ID_TOOLS_SETTINGS, MainWindow::OnSettings)
	EVT_MENU(ID_TOOLS_ALIGNMENT, MainWindow::OnAlignment)

	EVT_SPLITTER_SASH_POS_CHANGED(wxID_ANY, MainWindow::OnSplitterEvent)
	EVT_SPLITTER_DCLICK(wxID_ANY, MainWindow::OnSplitterEvent)

	EVT_MENU(wxID_SELECTALL, MainWindow::OnSelectAll)

	EVT_MENU(wxID_ABOUT, MainWindow::OnAbout)

	EVT_TIMER(wxID_ANY, MainWindow::OnRequestTimer)

	EVT_SIZE(MainWindow::OnSize)
	EVT_CLOSE(MainWindow::OnClose)
	EVT_ACTIVATE(MainWindow::OnActivate)
END_EVENT_TABLE()

class MainWindowDropTarget : public wxFileDropTarget {
	MainWindow *window;
public:
	MainWindowDropTarget(MainWindow *window) : window(window) {
	}
	bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames) {
		return window->OnDropFiles(x, y, filenames);
	}
};

MainWindow::MainWindow(AlignmentInfo *info) {
	InitMainWindow();
	RunAlignment(info);
	Show(1);
}

MainWindow::MainWindow(const wxString &path) {
	InitMainWindow();
	if (path.size()) {
		LoadPDB(path, 0);
	}
	Show(1);
}

void MainWindow::InitMainWindow()  {
	Create(0, wxID_ANY, APP_NAME, wxDefaultPosition, wxSize(config->defaultWidth, config->defaultHeight));
	// Smoother arcball;
	//config->rotationMode = 1;
	// Causes more issues than it's worth.
	// SetDoubleBuffered(1);

	numRenderWindows = 0;
	renderWindows = 0;
	renderWindowFrame = 0;

	statusBar = 0;
	sequenceWindow = 0;
	residueWindow = 0;


	request = 0;

	{
#ifdef WIN32
		wxIcon icon(Molli16x16_xpm);
#else
		wxIcon icon(Molli32x32_xpm);
#endif

		SetIcon(icon);
	}

	pdbManager = new PDBManager(this);
	mainWindows = (MainWindow**) realloc(mainWindows, sizeof(MainWindow)*(numMainWindows+1));
	mainWindows[numMainWindows++] = this;

	statusBar = new SharedStatusBar(this, pdbManager);
	SetStatusBar(statusBar);
	statusBar->SetDoubleBuffered(1);


	MenuEntry *entry = mainWindowMenu;
	SetMenuBar(CreateMenuBar(entry));

 	int x, y;
	GetClientSize(&x, &y);

	// Seems that size needs to be set for both windows before splitting or initializing either
	// for everything to work correctly.

	hSplitter = new wxSplitterWindow(this, wxID_ANY, wxPoint(0,0), wxSize(x, y), wxSP_NOBORDER | wxSP_LIVE_UPDATE);
	vSplitter = new wxSplitterWindow(hSplitter, wxID_ANY, wxPoint(0,0), wxSize(x, y), wxSP_NOBORDER | wxSP_LIVE_UPDATE);

	renderWindowFrame = new wxPanel(vSplitter);
	AddRenderWindow();
	LayoutRenderWindows();

	residueWindow = new ResidueWindow(vSplitter, this, pdbManager);
	sequenceWindow = new SequenceWindow(hSplitter, this, pdbManager);

	// Not quite sure why these end up being set fairly large at some point.
	// This is needed for free resizing.  Render window currently sets its own.
	residueWindow->SetMinSize(wxSize(0,0));
	sequenceWindow->SetMinSize(wxSize(0,0));

#ifdef WIN32
	if (config->showResidues) ToggleResidueWindow();
	else
#endif
	{
		residueWindow->Show(0);
		vSplitter->Initialize(renderWindowFrame);
	}

	if (config->showSequences) ToggleSequenceWindow();
	else {
		sequenceWindow->Show(0);
		hSplitter->Initialize(vSplitter);
	}



	wxSizer *sizer = new wxBoxSizer(wxVERTICAL);
	sizer->Add(hSplitter, 1, wxEXPAND);
	SetSizer(sizer);

	SetDropTarget(new MainWindowDropTarget(this));

	wxAcceleratorEntry table[] = {
		wxAcceleratorEntry(wxACCEL_CTRL, 'I', ID_DISPLAY_INVERT),

		wxAcceleratorEntry(wxACCEL_CTRL, 'A', wxID_SELECTALL),
		wxAcceleratorEntry(wxACCEL_NORMAL, WXK_BACK, ID_DISPLAY_HIDE),

		//wxAcceleratorEntry(wxACCEL_CTRL, 'Z', wxID_UNDO),
		//wxAcceleratorEntry(wxACCEL_CTRL|wxACCEL_SHIFT, 'Z', wxID_REDO),
		wxAcceleratorEntry(wxACCEL_CTRL, 'Y', wxID_REDO),
	};
	wxAcceleratorTable accel(sizeof(table)/sizeof(wxAcceleratorEntry), table);
	SetAcceleratorTable(accel);

	RequestTimer.SetOwner(this);
}


void MainWindow::AddRenderWindow(int restrictChain, int labelChain) {
	renderWindows = (RenderWindow**) realloc(renderWindows, sizeof(RenderWindow*) * (numRenderWindows+1));
	renderWindows[numRenderWindows++] = new RenderWindow(renderWindowFrame, this, pdbManager, restrictChain, labelChain);
}

void MainWindow::LayoutRenderWindows() {
	// Not deleting the old sizer first apparenty causes heap issues when
	// closing the window.
	renderWindowFrame->SetSizer(0, 1);
	int rows = 1;
	int cols = 1;
	while (rows * cols < numRenderWindows) {
		cols++;
		if (rows * cols >= numRenderWindows) break;
		rows++;
	}
	wxSizer *vsizer = new wxBoxSizer(wxVERTICAL);
	int index = 0;
	for (int r=0; r<rows; r++) {
		int tcols = cols;
		if (index + tcols*(rows - r) > numRenderWindows)
			tcols--;
		wxSizer *hsizer = new wxBoxSizer(wxHORIZONTAL);
		for (int c=0; c<tcols; c++) {
			if (c)
				hsizer->AddSpacer(2);
			hsizer->Add(renderWindows[index++], 1, wxEXPAND);
		}
		if (r)
			vsizer->AddSpacer(2);
		vsizer->Add(hsizer, 1, wxEXPAND);
	}
	renderWindowFrame->SetSizer(vsizer);
	renderWindowFrame->Layout();
}

void MainWindow::ChangeSelection(int chain, int index) {
	residueWindow->ChangeSelection(chain, index);
	sequenceWindow->ChangeSelection(chain, index);
}

void MainWindow::ContextMenu() {
	wxMenuBar *menuBar = GetMenuBar();
	if (!menuBar) return;
	wxMenu *item = 0;
	if (menuBar->FindItem(ID_DISPLAY_CARTOON, &item) && item) {
#ifdef WIN32
		wxPoint mouse = wxGetMousePosition();
		int x = mouse.x;
		int y = mouse.y;
		ScreenToClient(&x, &y);
		PopupMenu(item, x, y+2);
#else
		PopupMenu(item, wxDefaultPosition);
#endif
		//item->sh
		//item = item;
	}
}

void MainWindow::OnSize(wxSizeEvent& evt) {
	wxSize size = evt.GetSize();
	config->defaultWidth = size.GetWidth();
	if (config->defaultWidth < 60) config->defaultWidth = 60;
	config->defaultHeight = size.GetHeight();
	if (config->defaultHeight < 60) config->defaultHeight = 60;
	wxFrame::OnSize(evt);
}

void MainWindow::OnActivate(wxActivateEvent& evt) {
	int w;
	w=1;
}

void MainWindow::Redraw(unsigned int flags, int chain, int residue) {
	// Simplest just to always do this here.  Have to redraw in either
	// case, anyways.  Note that changing the selection implies checks need
	// to be rechecked in all cases.

	// At the moment, all flags except REDRAW_NOW are almost always set.
	// Only called on state changes (Selection change or rendering properties
	// changed).
	if (flags & (REDRAW_UPDATE_CHECKS | REDRAW_UPDATE_SELECTION))
		UpdateChecks();

	if (flags & REDRAW_RENDER) {
		static int base = 0;
		base ++;
		if (base > 100000) base = 0;
		for (int i=0; i<numRenderWindows; i++) {
			int index = (base + i) % numRenderWindows;
			renderWindows[index]->Refresh();
			if (flags & REDRAW_NOW) {
				renderWindows[index]->Update();
			}
		}
	}
	if (flags & REDRAW_RESIDUES) {
		residueWindow->UpdateDisplay(residue, chain);
		if (flags & REDRAW_NOW)
			residueWindow->Update();
	}
	if (flags & REDRAW_SEQUENCE) {
		if (chain >= 0) sequenceWindow->ChangeSelection(chain, residue);
		sequenceWindow->UpdateDisplay();
		if (flags & REDRAW_NOW)
			sequenceWindow->Update();
	}
}

void MainWindow::UpdateChecks() {
	unsigned int min, max;
	wxMenuBar *mb = GetMenuBar();
	if (!mb) return;

	if (pdbManager->numChains) {
		pdbManager->GetMinMaxFlags(0, &min, &max);
		if (max & RESIDUE_SELECTED) {
			pdbManager->GetMinMaxFlags(RESIDUE_SELECTED, &min, &max);
		}
	}
	else {
		max = min = 0;
	}

	mb->Check(ID_DISPLAY_CARTOON, (min & RESIDUE_RENDER_CARTOON)!=0);
	mb->Check(ID_DISPLAY_BALLANDSTICK, (min & RESIDUE_RENDER_BALL_AND_STICK)!=0);
	mb->Check(ID_DISPLAY_SPACEFILL, (min & RESIDUE_RENDER_SPACEFILL)!=0);

	mb->Check(ID_DISPLAY_LABEL, (min & RESIDUE_RENDER_LABEL)!=0);
	mb->Check(ID_DISPLAY_HYDROGEN_BONDS, (min & RESIDUE_RENDER_HBONDS)!=0);

	mb->Check(ID_DISPLAY_HIDE, (max & RESIDUE_RENDER)==0 && pdbManager->numChains);

	mb->Check(ID_DISPLAY_ATOM_COLORS, (min & RESIDUE_COLOR_ATOMS)!=0);
	mb->Check(ID_DISPLAY_COLORBYSTRUCTURE, (min & RESIDUE_COLOR_STRUCTURE)!=0);

	for (int i = ID_DISPLAY_CARTOON; i <= ID_DISPLAY_PICK_COLOR; i++) {
		mb->Enable(i, pdbManager->numChains != 0);
	}

	if (pdbManager->align) {
		mb->Check(ID_DISPLAY_COLORBYALIGNMENT, (min & RESIDUE_COLOR_ALIGNMENT) != 0);
		mb->Enable(ID_DISPLAY_COLORBYALIGNMENT, 1);
	}
	else {
		mb->Check(ID_DISPLAY_COLORBYALIGNMENT, 0);
		mb->Enable(ID_DISPLAY_COLORBYALIGNMENT, 0);
	}

	mb->Enable(wxID_UNDO, pdbManager->undo != 0);
	mb->Enable(wxID_REDO, pdbManager->redo != 0);

#ifdef WIN32
	mb->Check(ID_WINDOW_RESIDUE, vSplitter->IsSplit() != 0);
#endif
	mb->Check(ID_WINDOW_SEQUENCE, hSplitter->IsSplit() != 0);

	//*
	for (int i = ID_ROTATION_ARCBALL; i<=ID_ROTATION_DIRECT; i++) {
		mb->Check(i, config->rotationMode + ID_ROTATION_ARCBALL == i);
	}
	//*/
}

void MainWindow::SetDefaultDisplayModes() {
	pdbManager->UpdateSelectedResiduesColor(config->firstColor[ID_SECONDARY_COLOR_DEFAULT], 0);
	pdbManager->UpdateSelectedResidues(RESIDUE_RENDER_CARTOON | RESIDUE_COLOR_STRUCTURE | RESIDUE_COLOR_ATOMS, RESIDUE_RENDER_BALL_AND_STICK | RESIDUE_RENDER_SPACEFILL, 0);
	if (!pdbManager->align) {
		for (int i=0; i<pdbManager->numChains; i++) {
			ManagedChain *chain = pdbManager->chains[i];
			for (int j=chain->numChainResidues; j<chain->numTotalResidues; j++) {
				//if (strcmp(pdbManager->chains[i].chain->residues[j].resName, "HOH"))
				chain->residueInfo[j].flags |= RESIDUE_RENDER_SPACEFILL;
			}
		}
	}
	//pdbManager->UpdateSelectedResidues(RESIDUE_RENDER_BALL_AND_STICK | RESIDUE_COLOR_STRUCTURE | RESIDUE_COLOR_ATOMS, RESIDUE_RENDER_BALL_AND_STICK | RESIDUE_RENDER_SPACEFILL, 0);
}

int MainWindow::LoadPDB(const wxString &path, int requireEnd, const wxString &wxStringExtraComment) {
	// If old PDBManager is shared, create a new one.  Just to be safe,
	// don't free it until the end, though as loading files is single threaded,
	// shouldn't matter.
	PDBManager *oldManager = 0;
	if (pdbManager->IsShared()) {
		oldManager = pdbManager;
		pdbManager = new PDBManager(this);
	}
	else {
		pdbManager->ClearPDB();
	}
	PDBData *pdb = LoadPDBFromString(path);
	int res = 1;
	wxString status = wxEmptyString;

	wxFileName path2(path);
	wxString fname = path2.GetName();

	if (pdb) {
		if (requireEnd) {
			wxString error = wxEmptyString;
			if (pdb->error) {
				error =
					_T("Error reading pdb file.  This may\n")
					_T("indicate an incomplete or corrupted\n")
					_T("download.  Delete cached file?");
			}
			else if (!pdb->foundEnd) {
				error =
					_T("No END line in pdb file.  This may\n")
					_T("indicate an incomplete or corrupted\n")
					_T("download.  Delete cached file?");
			}
			if (!error.IsNull()) {
				if (wxMessageBox(error, _T("Warning"), wxYES_NO | wxICON_ERROR, this) == wxYES) {
					delete pdb;
					pdb = 0;
					status = wxString::Format(_T("Download of %s failed"), fname.c_str());
					wxRemoveFile(path);
				}
			}
		}
		if (pdb) {
			res = pdbManager->LoadPDB(pdb);
		}
	}

	SetDefaultDisplayModes();

	residueWindow->UpdatePDB(pdbManager, -1);
	sequenceWindow->UpdatePDB(pdbManager);
	RenderData *renderData = 0;
	renderWindows[0]->UpdatePDB(pdbManager, -1, 0, &renderData);
	if (numRenderWindows > 1) {
		while (numRenderWindows > 1) {
			delete renderWindows[--numRenderWindows];
		}
		LayoutRenderWindows();
	}

	if (!res) {
		status = wxString::Format(_T("%s loaded%s"), fname.c_str(), wxStringExtraComment.c_str());
		statusBar->UnlockText();
		statusBar->SetText(status);
		wxString end = path.AfterLast('.');
		wxFileName path2;
		if (end.CmpNoCase(_T("gz")) || end.CmpNoCase(_T("Z"))) {
			path2 = wxFileName(path.BeforeLast('.'));
		}
		else {
			path2 = wxFileName(path);
		}
		wxString title = path2.GetName();
		SetTitle(wxString::Format(APP_TITLE _T(" - %s"), title.c_str()));
	}
	else {
		if (status.IsEmpty())
			status = wxString::Format(_T("Unable to load %s%s"), fname.c_str(), wxStringExtraComment.c_str());
		statusBar->LockText(status);
		SetTitle(APP_TITLE);
	}
	if (oldManager) oldManager->RemoveWindow(this);

	Redraw();
	return res;
}

void MainWindow::OnOpen(wxCommandEvent& evt) {
	wxFileDialog fd(this, _T("Open PDB File..."), wxEmptyString, wxEmptyString, _T("PDB files (*.pdb;*.ent)|*.pdb;*.pdb.gz;*.pdb.Z;*.ent;*.ent.gz;*.ent.Z|All files (*.*)|*"), wxFD_OPEN | wxFD_FILE_MUST_EXIST);
	if (wxID_OK != fd.ShowModal()) return;
	wxString path = fd.GetPath();
	if (path.size()) {
		LoadPDB(path, 0);
	}
}

void MainWindow::OnOpenNew(wxCommandEvent& evt) {
	wxFileDialog fd(this, _T("Open PDB Files..."), wxEmptyString, wxEmptyString, _T("PDB files (*.pdb;*.ent)|*.pdb;*.pdb.gz;*.pdb.Z;*.ent;*.ent.gz;*.ent.Z|All files (*.*)|*"), wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_MULTIPLE);
	if (wxID_OK != fd.ShowModal()) return;
	wxArrayString paths;
	fd.GetPaths(paths);
	for (unsigned int i=0; i<paths.size(); i++) {
		wxFrame *frame = new MainWindow(paths.Item(i));
	}
}

void MainWindow::CleanupRequest() {
	if (request) {
		RequestTimer.Stop();
		delete request;
		request = 0;
	}
}

int MainWindow::UpdateRequest() {
	if (request->Update()) return 1;
	if (!request->m_happy) {
		ClearPDB(1);
		statusBar->LockText(request->m_error);
	}
	else {
		LoadPDB(request->m_path, 1, _T(" from remote server"));
	}
	CleanupRequest();
	return 0;
}

void MainWindow::ClearPDB(int refresh) {
	if (pdbManager->IsShared()) {
		pdbManager->RemoveWindow(this);
		pdbManager = new PDBManager(this);
	}
	else {
		pdbManager->ClearPDB();
	}
	residueWindow->UpdatePDB(pdbManager, -1);
	sequenceWindow->UpdatePDB(pdbManager);
	RenderData *renderData = 0;
	renderWindows[0]->UpdatePDB(pdbManager, -1, 0, &renderData);
	if (numRenderWindows > 1) {
		while (numRenderWindows > 1) {
			delete renderWindows[--numRenderWindows];
		}
		LayoutRenderWindows();
	}
	statusBar->UpdatePDB(pdbManager);
	//renderWindow->ResetCamera(1);
	if (refresh) Redraw();
}

void MainWindow::OnRequestTimer(wxTimerEvent &evt) {
	UpdateRequest();
}

void MainWindow::CompleteRequest(FileRequest *request) {
	if (request->m_localFile) {
		LoadPDB(request->m_path, 0, _T(" from local cache"));
		delete request;
		return;
	}
	this->request = request;
	wxString status = wxString::Format(_T("Requesting %s (%s)"), request->m_name.c_str(), request->m_urlString.c_str());
	statusBar->LockText(status);
	// This actually starts the http/ftp request.  Must be called
	// before timer is started.  Believe this is a wxWidgets 2.8 sockets bug.
	if (UpdateRequest()) {
		RequestTimer.Start(200);
	}
}

void MainWindow::OnOpenWeb(wxCommandEvent& evt) {
	CleanupRequest();
	int newWindow;
	int forceNoNewWindow = !pdbManager->numChains;
	FileRequest *request = OpenWebDialog(this, &newWindow, forceNoNewWindow);
	if (!request) {
		return;
	}
	if (!request->m_happy) {
		wxString status = wxString::Format(_T("Error requesting %s. Name or url may be invalid, or may be a connection issue."), request->m_urlString.c_str());
		statusBar->LockText(status);
		delete request;
		return;
	}

	MainWindow *window = this;
	if (request->m_newWindow)
		window = new MainWindow();
	window->CompleteRequest(request);
}

bool MainWindow::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& paths) {
	if (paths.size() == 1) {
		LoadPDB(paths.Item(0), 0);
	}
	else {
		for (unsigned int i=0; i<paths.size(); i++) {
			wxFrame *frame = new MainWindow(paths.Item(i));
		}
	}
	return 1;
}

MainWindow::~MainWindow() {
	free(renderWindows);
	renderWindows = 0;
	numRenderWindows = 0;

	CleanupRequest();
	if (pdbManager) pdbManager->RemoveWindow(this);
	for (int i=0; i<numMainWindows; i++) {
		if (mainWindows[i] == this) {
			mainWindows[i] = mainWindows[--numMainWindows];
			break;
		}
	}
	if (!numMainWindows) {
		free(mainWindows);
		mainWindows = 0;
	}
}

void MainWindow::OnExit(wxCommandEvent& evt) {
	// Order is such that it doesn't matter if their destructors
	// are called instantly or not.
	for (int i=numMainWindows-1; i>=0; i--) {
		mainWindows[i]->Close(1);
	}
}

void MainWindow::OnClose(wxCloseEvent& evt) {
	CleanupRequest();
	Destroy();
}

void MainWindow::OnSplitterEvent(wxSplitterEvent& evt) {
	wxEventType type = evt.GetEventType();
	if (type == wxEVT_COMMAND_SPLITTER_DOUBLECLICKED) {
		// Poorly documented function which apparently cancels the default bahavior,
		// which is hiding the second window.
		evt.Veto();
		if (evt.GetId() == vSplitter->GetId()) {
			config->residueSplitterPos = vSplitter->GetSashPosition();
			if (vSplitter->GetWindow1() == renderWindowFrame) {
				config->residueSplitterPos = config->residueSplitterPos - vSplitter->GetClientRect().GetRight();
			}
			config->residueSplitterPos = -config->residueSplitterPos;
			vSplitter->Unsplit(residueWindow);
			ToggleResidueWindow();
		}
		else {
			config->sequenceSplitterPos = hSplitter->GetSashPosition();
			if (hSplitter->GetWindow1() == vSplitter) {
				config->sequenceSplitterPos = config->sequenceSplitterPos - hSplitter->GetClientRect().GetBottom();
			}
			config->sequenceSplitterPos = -config->sequenceSplitterPos;
			hSplitter->Unsplit(sequenceWindow);
			ToggleSequenceWindow();
		}
	}
	else if (type == wxEVT_COMMAND_SPLITTER_SASH_POS_CHANGED) {
		config->residueSplitterPos = vSplitter->GetSashPosition();
		if (vSplitter->GetWindow1() == renderWindowFrame) {
			config->residueSplitterPos = config->residueSplitterPos - vSplitter->GetClientRect().GetRight();
		}

		config->sequenceSplitterPos = hSplitter->GetSashPosition();
		if (hSplitter->GetWindow1() == vSplitter) {
			config->sequenceSplitterPos = config->sequenceSplitterPos - hSplitter->GetClientRect().GetBottom();
		}
	}
}

void MainWindow::ToggleResidueWindow() {
	if (vSplitter->IsSplit()) {
		vSplitter->Unsplit(residueWindow);
	}
	else {
		//config->residueSplitterPos = -80;
		if (config->residueSplitterPos < 0) {
			vSplitter->SplitVertically(renderWindowFrame, residueWindow, config->residueSplitterPos);
			vSplitter->SetSashGravity(1.0);
		}
		else {
			vSplitter->SplitVertically(residueWindow, renderWindowFrame, config->residueSplitterPos);
			vSplitter->SetSashGravity(0.0);
		}
	}
	config->showResidues = vSplitter->IsSplit();
	UpdateChecks();
}

void MainWindow::ToggleSequenceWindow() {
	if (hSplitter->IsSplit()) {
		hSplitter->Unsplit(sequenceWindow);
	}
	else {
		if (config->sequenceSplitterPos < 0) {
			hSplitter->SplitHorizontally(vSplitter, sequenceWindow, config->sequenceSplitterPos);
			hSplitter->SetSashGravity(1.0);
		}
		else {
			hSplitter->SplitHorizontally(sequenceWindow, vSplitter, config->sequenceSplitterPos);
			hSplitter->SetSashGravity(0.0);
		}
	}
	config->showSequences = hSplitter->IsSplit();
	UpdateChecks();
}

void MainWindow::OnToggleSetting(wxCommandEvent& evt) {
	unsigned int id = evt.GetId();
	switch (id) {
		case ID_ROTATION_ARCBALL:
		case ID_ROTATION_SMOOTHED_ARCBALL:
		case ID_ROTATION_DIRECT:
			config->rotationMode = id - ID_ROTATION_ARCBALL;
	}
	for (int i=0; i<numMainWindows; i++) {
		mainWindows[i]->UpdateChecks();
	}
}

void MainWindow::OnToggleWindow(wxCommandEvent& evt) {
	switch(evt.GetId()) {
		case ID_WINDOW_RESIDUE:
			ToggleResidueWindow();
			break;
		case ID_WINDOW_SEQUENCE:
			ToggleSequenceWindow();
			break;
	}
}

void MainWindow::OnChangeViz(wxCommandEvent& evt) {
	pdbManager->SaveUndoState();
	// Currently redraw all as may eventually reflect visibility in text display.
	switch(evt.GetId()) {
		case ID_DISPLAY_CARTOON:
			pdbManager->ToggleSelectedFlags(RESIDUE_RENDER_CARTOON, RESIDUE_RENDER_SPACEFILL, 0, REDRAW_ALL);
			break;
		case ID_DISPLAY_BALLANDSTICK:
			pdbManager->ToggleSelectedFlags(RESIDUE_RENDER_BALL_AND_STICK, RESIDUE_RENDER_SPACEFILL, 0, REDRAW_ALL);
			break;
		case ID_DISPLAY_SPACEFILL:
			pdbManager->ToggleSelectedFlags(RESIDUE_RENDER_SPACEFILL, RESIDUE_RENDER_CARTOON|RESIDUE_RENDER_BALL_AND_STICK, 0, REDRAW_ALL);
			break;
		case ID_DISPLAY_HIDE:
			pdbManager->UpdateSelectedResidues(0, RESIDUE_RENDER, REDRAW_ALL);
			break;
		case ID_DISPLAY_HIDE_UNSELECTED:
			{
				unsigned int min, max;
				pdbManager->GetMinMaxFlags(0, &min, &max);
				// Don't do anything if nothing selected.
				// May eventually gray out option in this case.
				if (!(max & RESIDUE_SELECTED))
					break;
				pdbManager->UpdateResidues2(RESIDUE_SELECTED, 0, 0, RESIDUE_RENDER, 0);
				pdbManager->Redraw(REDRAW_ALL);
				break;
			}
		case ID_DISPLAY_INVERT:
			pdbManager->InvertSelection();
			break;
		case ID_DISPLAY_HYDROGEN_BONDS:
			pdbManager->ToggleSelectedFlags(RESIDUE_RENDER_HBONDS, 0, 0, REDRAW_ALL);
			break;
		case ID_DISPLAY_LABEL:
			pdbManager->ToggleSelectedFlags(RESIDUE_RENDER_LABEL, 0, 0, REDRAW_ALL);
			break;
	}
}

void MainWindow::OnChangeColor(wxCommandEvent& evt) {
	pdbManager->SaveUndoState();
	switch(evt.GetId()) {
		case ID_DISPLAY_ATOM_COLORS:
			pdbManager->ToggleSelectedFlags(RESIDUE_COLOR_ATOMS, 0, 0, REDRAW_RENDER);
			break;
		case ID_DISPLAY_COLORBYALIGNMENT:
			pdbManager->ToggleSelectedFlags(RESIDUE_COLOR_ALIGNMENT, RESIDUE_FORCED_COLOR, 0, REDRAW_ALL);
			break;
		case ID_DISPLAY_COLORBYSTRUCTURE:
			pdbManager->ToggleSelectedFlags(RESIDUE_COLOR_STRUCTURE, RESIDUE_FORCED_COLOR, 0, REDRAW_ALL);
			break;
		case ID_DISPLAY_PICK_COLOR:
			if (pdbManager->numChains) {
				static wxColourData data;
				data.SetChooseFull(1);
				Color color = config->secondaryColors[0];
				if (pdbManager->numChains) {
					color = pdbManager->GetResidueColor(0, 0, 1);
					for (int i=0; i<pdbManager->numChains; i++) {
						ManagedChain *chain = pdbManager->chains[i];
						for (int j=0; j<chain->numTotalResidues; j++) {
							if (chain->residueInfo[j].flags & RESIDUE_SELECTED) {
								color = pdbManager->GetResidueColor(i, j, 1);
								break;
							}
						}
					}
				}
				data.SetColour(WX_COLOR(color));
				wxColourDialog cd(this, &data);
				int res = cd.ShowModal();
				data = cd.GetColourData();
				if (res == wxID_OK) {
					// If all selected residues are being drawn in ball-and-stick mode,
					// unset RESIDUE_COLOR_ATOMS.  May eventually always do this, regardless.
					{
						unsigned int min, max;
						pdbManager->GetMinMaxFlags(0, &min, &max);
						if (max & RESIDUE_SELECTED) {
							pdbManager->GetMinMaxFlags(RESIDUE_SELECTED, &min, &max);
						}

						if ((min & RESIDUE_RENDER_3D) == RESIDUE_RENDER_BALL_AND_STICK) {
							pdbManager->ToggleSelectedFlags(0, RESIDUE_COLOR_ATOMS, RESIDUE_COLOR_ATOMS, 0);
						}
					}

					wxColour colour = data.GetColour();
					Color c = {colour.Red(), colour.Green(), colour.Blue(), 0xFF};
					pdbManager->UpdateSelectedResiduesColor(c, REDRAW_ALL & ~REDRAW_UPDATE_SELECTION);
				}
			}
			break;
	}
}

void MainWindow::OnChangeCamera(wxCommandEvent& evt) {
	// Currently all have same orientation.  May change, later.
	switch(evt.GetId()) {
		case ID_DISPLAY_CENTER_CAMERA:
			//renderWindow->ResetOrientation(1);
			renderWindows[0]->CenterCamera(1);
			break;
		case ID_DISPLAY_RESETZOOM:
			renderWindows[0]->ResetZoom(1);
			break;
		case ID_DISPLAY_RESETCAMERAPOSITION:
			renderWindows[0]->ResetCamera(1);
			break;
	}
}

void MainWindow::UpdatePDBManager(PDBManager *manager, int index, int flags, RenderData **renderData) {
	PDBManager *oldManager = 0;
	if (pdbManager != manager) {
		 oldManager = pdbManager;
		 pdbManager = manager;
		 pdbManager->AddWindow(this);
	}

	residueWindow->UpdatePDB(pdbManager, index);
	sequenceWindow->UpdatePDB(pdbManager);

	// index check shouldn't be needed.
	if (!(flags & ALIGNMENT_MULTIPLE_FRAMES) || index >= 0) {
		renderWindows[0]->UpdatePDB(pdbManager, index, 0, renderData);
		if (numRenderWindows > 1) {
			while (numRenderWindows > 1) {
				delete renderWindows[--numRenderWindows];
			}
			LayoutRenderWindows();
		}
		statusBar->UpdatePDB(pdbManager);
	}
	else {
		for (int i=0; i<pdbManager->numChains; i++) {
			if (numRenderWindows <= i) {
				AddRenderWindow(i, 1);
			}
			else
				renderWindows[i]->UpdatePDB(pdbManager, i, 1, renderData);
		}
		while (numRenderWindows > pdbManager->numChains) {
			delete renderWindows[--numRenderWindows];
		}
		LayoutRenderWindows();
	}

	if (oldManager) oldManager->RemoveWindow(this);
	if (index >= 0) {
		wxString name = wxString::FromAscii(pdbManager->chains[index]->idString);
		wxString s = wxString::Format(APP_TITLE _T(" - Alignment (%s)"), name.c_str());
		SetTitle(s);
	}
	else {
		SetTitle(APP_TITLE _T(" - Alignment"));
	}
}

int MainWindow::LoadAlignment(MultipleAlignment *ma, int flags) {
	// If old PDBManager is shared, create a new one.  Just to be safe,
	// don't free it until the end, though as loading files is single threaded,
	// shouldn't matter.
	PDBManager *newManager = pdbManager;
	if (pdbManager->IsShared()) {
		newManager = new PDBManager(this);
	}

	newManager->LoadAlignment(ma);

	SetDefaultDisplayModes();
	RenderData *renderData = 0;

	if (flags & ALIGNMENT_MULTIPLE_WINDOWS) {
		UpdatePDBManager(newManager, 0, 0, &renderData);
		for (int i=1; i<pdbManager->numChains; i++) {
			MainWindow *mw = new MainWindow();
			mw->UpdatePDBManager(newManager, i, 0, &renderData);
		}
	}
	else {
		UpdatePDBManager(newManager, -1, flags, &renderData);
	}

	statusBar->UnlockText();
	statusBar->SetText(_T("Alignment done."));

	Redraw();
	return 1;
}

void MainWindow::RunAlignment(AlignmentInfo *info) {
	PDBChain **chains = (PDBChain**) calloc(info->numPDB, sizeof(PDBChain*));
	PDBData *pdb = 0;
	PDBModel *model = 0;

	int i;
	int numChains = info->numPDB;
	for (i=0; i < numChains; i++) {
		PDBInfo *inf = info->pdb[i];
		if (model && inf->modelNum == info->pdb[i-1]->modelNum &&
			inf->name.IsSameAs(info->pdb[i-1]->name)) {
				chains[i] = PopChain(model, info->pdb[i]->chain);
		}
		if (chains[i]) continue;
		if (pdb) CleanupPDB(pdb);
		model = 0;
		pdb = LoadPDBFromString(inf->name);
		if (!pdb) break;
		model = FindModel(pdb, info->pdb[i]->modelNum);
		if (!model) break;
		chains[i] = PopChain(model, info->pdb[i]->chain);
		if (!chains) break;
	}
	if (pdb) CleanupPDB(pdb);
	if (i < numChains) {
		PDBInfo *inf = info->pdb[i];
		wxString msg = wxString::Format(_T("Couldn't find %s, model %i, chain %c.\n\nAlignment aborted."), inf->name.c_str(), inf->modelNum, inf->chain);
		wxMessageBox(msg, _T("Error reading file"), wxOK | wxICON_ERROR, this);
	}
	else {
		InitMultipleAlignmentTables();
		MultipleAlignment *ma = Align(chains, numChains, 0, 0);
		if (!ma || !ma->numResidues) {
			if (ma) CleanupAlignment(ma);
			for (int i=0; i<ma->numChains; i++) {
				free(ma->chains[i]->res);
				free(ma->chains[i]);
			}
			wxMessageBox(_T("Unable to align structures."), _T("Alignment Failed"), wxOK | wxICON_ERROR, this);
		}
		else {
			int *originalOrder = (int*) malloc(sizeof(int) * numChains * 2);
			int *newOrder = originalOrder + numChains;
			int coreSize = ma->numResidues;

			/* Current order indicates reference structure. */
			for (i=0; i<ma->numChains; i++) {
				originalOrder[i] = i;
				newOrder[i] = ma->chains[i]->id;
			}

			UnbentRMSDAlign(ma);
			double rmsdCutoff = 5.0;
			while (1) {
				FinalExtend(ma, rmsdCutoff);
				if (coreSize == ma->numResidues) break;
				UnbentRMSDAlign(ma);
				coreSize = ma->numResidues;
			}
			ReorderAlignment(ma, originalOrder);
			if (ma->numChains > 2)
				CalcPartial(ma, rmsdCutoff);
			LoadAlignment(ma, info->flags);

			free(originalOrder);
		}
	}

	free(chains);
	delete info;
}

void MainWindow::OnAlignment(wxCommandEvent& evt) {
	wxString path = config->strings.pdbPath;
	// TODO:  Set to current model's path.
	//if (this->pdbManager->numModels)
		//pdbManager->models[0].model
	int forceNoNewWindow = !pdbManager->numChains;
	AlignmentInfo *info = MultipleAlignmentDialog(this, path, forceNoNewWindow);
	if (!info) return;
	if (info->flags & ALIGNMENT_NEW_WINDOW) {
		new MainWindow(info);
	}
	else {
		RunAlignment(info);
	}
}

void MainWindow::OnSettings(wxCommandEvent& evt) {
	if (wxID_OK == config->ShowModal(this)) {
		// Must do this for all render windows first, as
		// may share data.
		for (int i=0; i<numMainWindows; i++) {
			for (int j=0; j<mainWindows[i]->numRenderWindows; j++) {
				mainWindows[i]->renderWindows[j]->ConfigChangedUninit();
			}
		}
		for (int i=0; i<numMainWindows; i++) {
			for (int j=0; j<mainWindows[i]->numRenderWindows; j++) {
				mainWindows[i]->renderWindows[j]->ConfigChangedInit();
			}
			mainWindows[i]->Redraw(REDRAW_ALL);
		}
	}
}

void MainWindow::OnSelectAll(wxCommandEvent& evt) {
	unsigned int min, max;
	pdbManager->GetMinMaxFlags(0, &min, &max);
	pdbManager->UpdateResidues(0, RESIDUE_SELECTED&~min, RESIDUE_LAST_SELECTED | (RESIDUE_SELECTED&min), REDRAW_ALL);
}

void MainWindow::OnUndo(wxCommandEvent& evt) {
	switch(evt.GetId()) {
		case wxID_UNDO:
			pdbManager->Undo();
			break;
		case wxID_REDO:
			pdbManager->Redo();
			break;
	}
}
#include <wx/statline.h>

void MainWindow::OnAbout(wxCommandEvent& evt) {
	wxMozillaLikeAboutBoxDialog about(this);
	about.SetCopyright(_T("(C) 2012 Matt Menke (MattMenke@gmail.com)\nLicense: GPL v2"));
	about.SetAppName(APP_NAME);
	about.SetVersion(APP_VERSION);
	about.SetHeaderBitmap(wxGetBitmapFromMemory(header_png, sizeof(header_png)));
	wxString buildInfo =
#ifdef _OPENMP
			_T("OpenMP build\n");
#else
			_T("Single threaded build\n");
#endif
	buildInfo << wxMozillaLikeAboutBoxDialog::GetBuildInfo(wxMozillaLikeAboutBoxDialog::wxBUILDINFO_LONG);
	about.SetCustomBuildInfo(buildInfo);
	about.ApplyInfo();
	about.ShowModal();
	//wxAboutBox(info);
}
